<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="robots" content="noodp" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
        <title>Redis中的多路复用机制 - 负责和大饼的博客</title><meta name="Description" content="想专注于学点手艺"><meta property="og:title" content="Redis中的多路复用机制" />
<meta property="og:description" content="Redis中的多路复用机制 Redis服务端对于命令的处理是单线程的，但是在I/O层面却可以同时面对多个客户端并发的提供服务，并发到内部单线程" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://amath0312.github.com/epoll-in-redis/" />
<meta property="article:published_time" content="2020-11-08T14:17:31+08:00" />
<meta property="article:modified_time" content="2020-11-08T14:17:31+08:00" />
<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="Redis中的多路复用机制"/>
<meta name="twitter:description" content="Redis中的多路复用机制 Redis服务端对于命令的处理是单线程的，但是在I/O层面却可以同时面对多个客户端并发的提供服务，并发到内部单线程"/>
<meta name="application-name" content="负责和大饼的博客">
<meta name="apple-mobile-web-app-title" content="负责和大饼的博客"><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
        <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
        <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="manifest" href="/site.webmanifest"><link rel="canonical" href="https://amath0312.github.com/epoll-in-redis/" /><link rel="prev" href="https://amath0312.github.com/about-cache-line/" /><link rel="stylesheet" href="/lib/normalize/normalize.min.css"><link rel="stylesheet" href="/css/style.min.css"><link rel="stylesheet" href="/lib/fontawesome-free/all.min.css"><link rel="stylesheet" href="/lib/animate/animate.min.css"><script type="application/ld+json">
    {
        "@context": "http://schema.org",
        "@type": "BlogPosting",
        "headline": "Redis中的多路复用机制",
        "inLanguage": "zh-CN",
        "mainEntityOfPage": {
            "@type": "WebPage",
            "@id": "https:\/\/amath0312.github.com\/epoll-in-redis\/"
        },"genre": "posts","keywords": "io, epoll, redis","wordcount":  1939 ,
        "url": "https:\/\/amath0312.github.com\/epoll-in-redis\/","datePublished": "2020-11-08T14:17:31+08:00","dateModified": "2020-11-08T14:17:31+08:00","publisher": {
            "@type": "Organization",
            "name": "负责和大饼"},"author": {
                "@type": "Person",
                "name": "负责和大饼"
            },"description": ""
    }
    </script></head>
    <body header-desktop="" header-mobile=""><script type="text/javascript">(window.localStorage && localStorage.getItem('theme') ? localStorage.getItem('theme') === 'dark' : ('' === 'auto' ? window.matchMedia('(prefers-color-scheme: dark)').matches : '' === 'dark')) && document.body.setAttribute('theme', 'dark');</script>

        <div id="mask"></div><div class="wrapper"><header class="desktop" id="header-desktop">
    <div class="header-wrapper">
        <div class="header-title">
            <a href="/" title="负责和大饼的博客">负责和大饼的博客</a>
        </div>
        <div class="menu">
            <div class="menu-inner"><a class="menu-item" href="/posts/"> Blog </a><a class="menu-item" href="/categories/"> Categories </a><a class="menu-item" href="/tags/"> Tags </a><a class="menu-item" href="/about/"> About </a><span class="menu-item delimiter"></span><span class="menu-item search" id="search-desktop">
                        <input type="text" placeholder="Search titles or contents..." id="search-input-desktop">
                        <a href="javascript:void(0);" class="search-button search-toggle" id="search-toggle-desktop" title="Search">
                            <i class="fas fa-search fa-fw"></i>
                        </a>
                        <a href="javascript:void(0);" class="search-button search-clear" id="search-clear-desktop" title="Clear">
                            <i class="fas fa-times-circle fa-fw"></i>
                        </a>
                        <span class="search-button search-loading" id="search-loading-desktop">
                            <i class="fas fa-spinner fa-fw fa-spin"></i>
                        </span>
                    </span><a href="javascript:void(0);" class="menu-item theme-switch" title="Switch Theme">
                    <i class="fas fa-adjust fa-fw"></i>
                </a>
            </div>
        </div>
    </div>
</header><header class="mobile" id="header-mobile">
    <div class="header-container">
        <div class="header-wrapper">
            <div class="header-title">
                <a href="/" title="负责和大饼的博客">负责和大饼的博客</a>
            </div>
            <div class="menu-toggle" id="menu-toggle-mobile">
                <span></span><span></span><span></span>
            </div>
        </div>
        <div class="menu" id="menu-mobile"><div class="search-wrapper">
                    <div class="search mobile" id="search-mobile">
                        <input type="text" placeholder="Search titles or contents..." id="search-input-mobile">
                        <a href="javascript:void(0);" class="search-button search-toggle" id="search-toggle-mobile" title="Search">
                            <i class="fas fa-search fa-fw"></i>
                        </a>
                        <a href="javascript:void(0);" class="search-button search-clear" id="search-clear-mobile" title="Clear">
                            <i class="fas fa-times-circle fa-fw"></i>
                        </a>
                        <span class="search-button search-loading" id="search-loading-mobile">
                            <i class="fas fa-spinner fa-fw fa-spin"></i>
                        </span>
                    </div>
                    <a href="javascript:void(0);" class="search-cancel" id="search-cancel-mobile">
                        Cancel
                    </a>
                </div><a class="menu-item" href="/posts/" title="">Blog</a><a class="menu-item" href="/categories/" title="">Categories</a><a class="menu-item" href="/tags/" title="">Tags</a><a class="menu-item" href="/about/" title="">About</a><a href="javascript:void(0);" class="menu-item theme-switch" title="Switch Theme">
                <i class="fas fa-adjust fa-fw"></i>
            </a></div>
    </div>
</header>
<div class="search-dropdown desktop">
    <div id="search-dropdown-desktop"></div>
</div>
<div class="search-dropdown mobile">
    <div id="search-dropdown-mobile"></div>
</div>
<main class="main">
                <div class="container"><div class="toc" id="toc-auto">
            <h2 class="toc-title">Contents</h2>
            <div class="toc-content" id="toc-content-auto"></div>
        </div><article class="page single"><h1 class="single-title animated flipInX">Redis中的多路复用机制</h1><div class="post-meta">
            <div class="post-meta-line"><span class="post-author"><a href="/" title="Author" rel=" author" class="author"><i class="fas fa-user-circle fa-fw"></i>负责和大饼</a></span>&nbsp;<span class="post-category">included in <a href="/categories/dev/"><i class="far fa-folder fa-fw"></i>dev</a>&nbsp;<a href="/categories/redis/"><i class="far fa-folder fa-fw"></i>redis</a>&nbsp;<a href="/categories/io/"><i class="far fa-folder fa-fw"></i>io</a></span></div>
            <div class="post-meta-line"><i class="far fa-calendar-alt fa-fw"></i>&nbsp;<time datetime="2020-11-08">2020-11-08</time>&nbsp;<i class="fas fa-pencil-alt fa-fw"></i>&nbsp;1939 words&nbsp;
                <i class="far fa-clock fa-fw"></i>&nbsp;4 minutes&nbsp;</div>
        </div><div class="details toc" id="toc-static"  kept="">
                <div class="details-summary toc-title">
                    <span>Contents</span>
                    <span><i class="details-icon fas fa-angle-right"></i></span>
                </div>
                <div class="details-content toc-content" id="toc-content-static"><nav id="TableOfContents">
  <ul>
    <li><a href="#io处理流程">IO处理流程</a></li>
    <li><a href="#redis的多路复用框架使用的非阻塞的数据返回模式">redis的多路复用框架使用的非阻塞的数据返回模式</a>
      <ul>
        <li><a href="#select和poll">select和poll</a></li>
        <li><a href="#epoll的多路复用机制">epoll的多路复用机制</a></li>
      </ul>
    </li>
  </ul>
</nav></div>
            </div><div class="content" id="content"><h1 id="redis中的多路复用机制">Redis中的多路复用机制</h1>
<blockquote>
<p>Redis服务端对于命令的处理是<strong>单线程</strong>的，但是在I/O层面却可以同时面对多个客户端并发的提供服务，并发到内部单线程的转化通过多路复用框架实现。</p>
</blockquote>
<h2 id="io处理流程">IO处理流程</h2>
<p>一个IO操作的完整流程是数据请求先从用户态到内核态，也就是操作系统层面，然后再调用操作系统提供的API，调用相应的设备去获取相应的数据。当相应的设备准备好数据后，会将数据复制到内核态。</p>
<p>数据从相应的设备到内核态的处理方式分为阻塞和非阻塞：</p>
<ul>
<li>
<p>阻塞：用户请求会等待数据准备好从操作系统调用相应的设备返回到内核态，如果没有返回处于阻塞状态。</p>
</li>
<li>
<p>非阻塞：非阻塞是操作系统接收到一组文件描述符，然后操作系统批量处理这些个文件描述符，然后不管有没有准备好数据都立即返回，如果没有对应的准备好的文件描述符，则继续轮询获取准备好数据的文件描述符。</p>
</li>
</ul>
<p>数据从内核态到用户态的复制数据的处理方式的不同可分为同步和异步：</p>
<ul>
<li>
<p>同步：用户请求等待数据从内核态向用户态复制数据，在此期间不做其他的事情</p>
</li>
<li>
<p>异步：在数据从内核态向用户态复制的过程中，用户请求不会一直处于等待状态而是做其他事情</p>
</li>
</ul>
<h2 id="redis的多路复用框架使用的非阻塞的数据返回模式">redis的多路复用框架使用的非阻塞的数据返回模式</h2>
<blockquote>
<p>使用模型有：select、poll、epoll</p>
</blockquote>
<h3 id="select和poll">select和poll</h3>
<p>select、poll都是传递一个fd的集合调用操作系统的API。其中，select 模式有大小的限制最大只能是1024的fd；而 poll是传递一个pollfd数组，pollfd中的events字段和revents分别用于标示关注的事件和发生的事件，所以没有大小限制。</p>
<p>select/poll的几大缺点：</p>
<ol>
<li>
<p>每次调用select/poll，都需要把fd集合从用户态copy到内核态，如果是fd集合很大的时候，开销会很大；</p>
</li>
<li>
<p>每次调用select/poll，都要在内核态中循环遍历fd集合，去判断哪些fd准备好了数据，在fd集合很大的情况下开销会很大；</p>
</li>
<li>
<p>针对select支持的文件描述符数量太小了，默认是1024</p>
</li>
<li>
<p>select返回的是整个有句柄的数组，需要全部遍历一遍才知道那些fd有事件的产生</p>
</li>
<li>
<p>因为poll使用的是链表保存fd，所以没有fd大小数量的限制，但是其他的缺点依然存在</p>
</li>
</ol>
<h3 id="epoll的多路复用机制">epoll的多路复用机制</h3>
<p>epoll支持的fd上限为文件打开数，这个数字一般远大于2048。例如，在1GB内存的机器上大约是10万左右。</p>
<p>设想一下如下场景：有100万个客户端同时与一个服务器进程保持着TCP连接，而每一时刻，通常只有几百上千个TCP连接是活跃的（事实上大部分场景都是这种情况），如何实现这样的高并发？</p>
<p>在select/poll时代，会把这些fd从用户态复制到内核态，然后让操作系统去查询这些套接字上是否有事件发生，轮询完以后，再将这些句柄集合返回，从内核态复制会用户态，让用户应用程序遍历轮询去获取有哪些句柄有事件的发上，这个过程大量的浪费了性能。</p>
<p>因此，select和poll一般只能处理几千的并发。</p>
<p>但是epoll的设计和select/poll的设计完全不同。他不需要用户应用程序循环遍历所有的句柄数组才能知道有哪些句柄有事件的发生，因此epoll能支持更大的并发。</p>
<p>epoll 在linux系统内部维护了一个文件系统结构，将select/poll分为了3个部分：</p>
<ol>
<li>
<p>调用epoll_create在linux系统中维护一个epoll对象。每一个epoll对象都有一个独立的<code>eventpoll</code>结构体，用于存放需要监控的fd事件，这些事件都会挂载在结构体重的红黑树中。而所有添加到eventpoll结构体中，都会与设备(网卡)建立回调关系。也就是说，当有事件发生时候，相应的事件会回调这个回调方法。这个回调方法就是ep_poll_callback，他将发生的事件添加到eventpoll结构体中的rdlist双向链表中。</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="k">struct</span> <span class="n">eventpoll</span><span class="p">{</span>
   <span class="c1">//...........
</span><span class="c1"></span>   
   <span class="cm">/**红黑树的根节点，存储了所有添加到epoll中所需要监控的事件**/</span>
   <span class="k">struct</span> <span class="n">rb_root</span> <span class="n">rb</span><span class="p">;</span>
   
   <span class="cm">/** 双向链表中存储着所有的通过epoll_wait返回给用户满足条件的事件 **/</span>    
   <span class="k">struct</span> <span class="n">list_head</span> <span class="n">rdlist</span><span class="p">;</span>
   
   <span class="c1">//..............
</span><span class="c1"></span> <span class="p">};</span>
</code></pre></div></li>
<li>
<p>调用epoll_ctl向epoll对象中添加100万个fd 套接字。</p>
</li>
<li>
<p>调用epoll_wait收集事件发生的fd。在epoll中，对于每一个事件，都会建立一个epitem结构体，当调用event_wait判断是否有事件发生时候，只需要判断eventpoll双向链表（<code>rdlist</code>）有没有epitem对象即可。如果rdlist不为空，则直接将发生的事件复制到用户态，同时将事件数量返回给用户。</p>
<div class="highlight"><pre class="chroma"><code class="language-c" data-lang="c"><span class="k">struct</span> <span class="n">epitem</span><span class="p">{</span>
    <span class="k">struct</span> <span class="n">rb_node</span> <span class="n">rbn</span><span class="p">;</span><span class="c1">//红黑树节点
</span><span class="c1"></span>    <span class="k">struct</span> <span class="n">list_head</span> <span class="n">rdllink</span><span class="p">;</span><span class="c1">//双向链表节点
</span><span class="c1"></span>    <span class="k">struct</span> <span class="n">epoll_field</span> <span class="n">ffd</span><span class="p">;</span><span class="c1">//事件句柄信息
</span><span class="c1"></span>    <span class="k">struct</span> <span class="n">eventpoll</span> <span class="o">*</span><span class="n">ep</span><span class="p">;</span><span class="c1">//指向所属的eventpoll对象
</span><span class="c1"></span>    <span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">event</span><span class="err">；</span><span class="c1">//期待发生的事件类型
</span><span class="c1"></span><span class="p">};</span>    
</code></pre></div></li>
</ol>
<p>参考文章：<a href="https://www.cnblogs.com/histlyb/p/12155934.html" target="_blank" rel="noopener noreffer">redis多路复用的理解</a></p>
</div><div class="post-footer" id="post-footer">
    <div class="post-info">
        <div class="post-info-line">
            <div class="post-info-mod">
                <span>Updated on 2020-11-08</span>
            </div>
            <div class="post-info-license"></div>
        </div>
        <div class="post-info-line">
            <div class="post-info-md"></div>
            <div class="post-info-share">
                <span></span>
            </div>
        </div>
    </div>

    <div class="post-info-more">
        <section class="post-tags"><i class="fas fa-tags fa-fw"></i>&nbsp;<a href="/tags/io/">io</a>,&nbsp;<a href="/tags/epoll/">epoll</a>,&nbsp;<a href="/tags/redis/">redis</a></section>
        <section>
            <span><a href="javascript:void(0);" onclick="window.history.back();">Back</a></span>&nbsp;|&nbsp;<span><a href="/">Home</a></span>
        </section>
    </div>

    <div class="post-nav"><a href="/about-cache-line/" class="prev" rel="prev" title="Cache Line对读写性能的影响"><i class="fas fa-angle-left fa-fw"></i>Cache Line对读写性能的影响</a></div>
</div>
</article></div>
            </main><footer class="footer">
        <div class="footer-container"><div class="footer-line">Powered by <a href="https://gohugo.io/" target="_blank" rel="noopener noreffer" title="Hugo 0.75.1">Hugo</a> | Theme - <a href="https://github.com/dillonzq/LoveIt" target="_blank" rel="noopener noreffer" title="LoveIt 0.2.10"><i class="far fa-kiss-wink-heart fa-fw"></i> LoveIt</a>
                </div><div class="footer-line"><i class="far fa-copyright fa-fw"></i><span itemprop="copyrightYear">2019 - 2020</span><span class="author" itemprop="copyrightHolder">&nbsp;<a href="/" target="_blank"></a></span>&nbsp;|&nbsp;<span class="license">本文采用<a rel="license" href="http://creativecommons.org/licenses/by-nc/4.0/" target="_blank">知识共享署名-非商业性使用 4.0 国际许可协议</a>进行许可</span></div>
        </div>
    </footer></div>

        <div id="fixed-buttons"><a href="#" id="back-to-top" class="fixed-button" title="Back to Top">
                <i class="fas fa-arrow-up fa-fw"></i>
            </a><a href="#" id="view-comments" class="fixed-button" title="View Comments">
                <i class="fas fa-comment fa-fw"></i>
            </a>
        </div><script type="text/javascript" src="/lib/smooth-scroll/smooth-scroll.min.js"></script><script type="text/javascript" src="/lib/autocomplete/autocomplete.min.js"></script><script type="text/javascript" src="/lib/algoliasearch/algoliasearch-lite.umd.min.js"></script><script type="text/javascript" src="/lib/lazysizes/lazysizes.min.js"></script><script type="text/javascript" src="/lib/clipboard/clipboard.min.js"></script><script type="text/javascript">window.config={"code":{"copyTitle":"Copy to clipboard","maxShownLines":10},"comment":{},"search":{"algoliaAppID":"JJRDOPDPLW","algoliaIndex":"amath_blog","algoliaSearchKey":"e529bedc6a3af35b53e2597f1c7550c2","highlightTag":"em","maxResultLength":10,"noResultsFound":"No results found","snippetLength":50,"type":"algolia"}};</script><script type="text/javascript" src="/js/theme.min.js"></script></body>
</html>
